%%% Copyright [heng] [litaocheng@gmail.com] 
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License. 
%%% You may obtain a copy of the License at 
%%%    http://www.apache.org/licenses/LICENSE-2.0 
%%% Unless required by applicable law or agreed to in writing, 
%%% software distributed under the License is distributed on an "AS IS" BASIS, 
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
%%% See the License for the specific language governing permissions and 
%%% limitations under the License. 

%%% @doc heng_master_worker the worker node manager
%%%  use the ets save the worker's info
-module(hengm_worker).
-author('litaocheng@gmail.com').
-vsn('0.1').
-include("heng_master.hrl").

-behaviour(gen_server).

-export([start_link/0]).
%% manager the worker node
-export([add/1, delete/1, modify/2]).
-export([get_worker/1]).

-export([statis/0, statis/1]).

%% about the remote worker
-export([start_remote_worker/1, stop_remote_worker/1]).
-export([worker_statis/1]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2
        , code_change/3, terminate/2).

-record(state, 
            {             
            }).            

-define(TAB_WORKER, tab_worker).
-define(TAB_TASK, tab_task).

%% @doc start the server
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%% @doc add worker to the system
-spec add(Worker :: worker()) ->
    'ok' | {'error', ereason()}.
add(Worker) ->
    ?Debug("~p add worker:~p~n", [?MODULE, Worker]),
    case gen_server:call(?SERVER, {add, Worker}) of
        true ->
            ok;
        false -> % the already exist
            {error, key_exist}
    end.

%% @doc delete on worker node from the system
-sepc delete(WNode :: node()) ->
    'ok' | {'error', ereason()}.
delete(WNode) ->
    ?Debug("~p delete worker:~p~n", [?MODULE, WNode]),
    gen_server:call(?SERVER, {delete, WNode}).

%% @doc modify the worker info
-sepc modify(WNode :: node(), Values :: tuple()) ->
    'ok' | {'error', ereason()}.    
modify(WNode, {Field, Value}) ->
    ?Debug("~p modify worker:~p~n", [?MODULE,WNode]), 
    % first get the node
    case get_worker(WNode) of
        {ok, Worker} ->
            W2 = heng_util:update_record(Field, Value, worker, Worer),
            gen_server:call(?SERVER, {modify, WNode, W2});
        {error, R} ->
            {error, R}
    end.
    
%% @doc get the node info
-spec get_worker(WNode :: node()) ->
    {'ok', worker()} | {'error', ereason()}.
get_worker(WNode) ->
    gen_server:call(?SERVER, {get_worker, WNode}).
    
%% @doc get the full info of all the workers
-sepc statis() ->
    [tuple()].
statis() ->
    statis([]). 

%% @doc get the specify item of all the workers 
-spec statis(Item :: [atom()]) ->
    [any()].
statis(Items) ->
    ?Debug("~p statis :~p~n", [?MODULE, Items]),
    gen_server:call(?SERVER, {statis, Items}).

%% @doc start the woker on remote machie
%%  the heng_master_worker_starter do it!
-spec start_remote_worker(Worker :: worker()) ->
    'ok' | {'error', ereason()}.
start_remote_worker(Worker) ->
    ?Debug("start remote worker:~p", [Worker]),
    gen_server:call(?Worker_Starter, {start, Worker}).

%% @doc start the woker on remote machie
-spec stop_remote_worker(Node :: node()) ->
    'ok' | {'error', ereason()}.
stop_remote_worker(Node) ->
    gen_server:call(?Worker_Starter, {stop, Node}).

%% @doc get the worker statis info
-spec worker_statis(Node :: node()) ->
    {'ok', [{atom(), term()}]} | {'error', ereason()}.
worker_statis(Node) ->
    case get_worker(Node) of
        {ok, Worker = #worker{replast = Statis}} ->
            {ok, Statis};
        {error, R} ->
            {error, R}
    end.

%% gen_server callbacks
init(_Args) ->
    process_flag(trapexit, true),
    ets:new(?TAB_WORKER, [named_table, set, protected, {keypos, #worker.node}]),
    ets:new(?TAB_TASK, [named_table, set, protected, {keypos, #worker.node}]),
    {ok, []}.

handle_call({add, Worker}, _From, State) ->
    Reply = ets:insert_new(?TABLE_WORKER, Worker),
    {reply, Reply, State};
handle_call({delete, Node}, _From, State) ->
    ets:delete(?TABLE_WORKER, Node),
    {reply, ok, State};
handle_call({modify, _Node, Worker}, _From, State) ->
    ets:insert(?TABLE_WORKER, Worker),
    {reply, ok, State};
handle_call({get_worker, Node}, _From, State) ->
    Reply =
    case ets:lookup(?TABLE_WORERK, Node) of
        [] ->
            {error, key_not_exist};
        [W] ->
            {ok, Worker}
    end,
    {reply, Reply, State};
handle_call({statis, Items}, _From, State) ->
    Fun = 
    fun(W, AccIn) ->
        W2 = filter_items(W, Items),
        [W2 | AccIn]
    end,
    Reply = lists:reverse(ets:foldl(Fun, [], ?TAB_WORKER)),
    {reply, Reply, State};
handle_call(_Msg, _From, State) ->
    {noreply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_Old, State, _Extra) ->
    {ok, State}.


%%
%% internal API
%%

%% filter the filed in the worker entry
filter_items(W = #worker{}, Items) when is_list(Items) ->
    Fields = record_info(fields, worker),
    filter_items0(Fields, Items, W).

filter_items0(Fields, [], W) ->
    filter_items0(Fields, Fields, W);
filter_items0(Fields, Fields = [_|_], W) ->
    lists:zip(Fields, tuple_to_list(W));
filter_items0(Fields, Items, W) ->
    Vs =
    lists:foldl(
        fun(Item, AccIn) ->
            case heng_util:pos(Item, Fields) of
                0 -> % item is not in fields
                    AccIn;
                N ->
                    V = element(N + 1, W),
                    [{Item, V} | AccIn]
            end
        end,
        [],
        Items),
    lists:reverse(Vs).
